package cache

import (
	"sync"
	"time"
)

type item[V any] struct {
	Value      V
	Created    time.Time
	Expiration int64
}

// Cache хранилище данных в памяти.
type Cache[K comparable, V any] struct {
	mutex             sync.RWMutex
	items             map[K]item[V]
	defaultExpiration time.Duration
	cleanupInterval   time.Duration
}

// NewCache инициализирует новый кеш в памяти.
func NewCache[K comparable, V any](defaultExpiration, cleanupInterval time.Duration) *Cache[K, V] {
	cache := Cache[K, V]{
		defaultExpiration: defaultExpiration,
		cleanupInterval:   cleanupInterval,
		items:             make(map[K]item[V]),
	}

	if cleanupInterval > 0 {
		go cache.cleanup()
	}

	return &cache
}

func (c *Cache[K, V]) set(key K, value V, duration time.Duration) {
	if duration == 0 {
		duration = c.defaultExpiration
	}

	var expiration int64
	if duration > 0 {
		expiration = time.Now().Add(duration).UnixNano()
	}

	c.items[key] = item[V]{
		Value:      value,
		Expiration: expiration,
		Created:    time.Now(),
	}
}

// Set устанавливает значение.
func (c *Cache[K, V]) Set(key K, value V, duration time.Duration) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.set(key, value, duration)
}

func (c *Cache[K, V]) get(key K) (V, bool) {
	item, found := c.items[key]
	if !found {
		return *new(V), false
	}

	if item.Expiration > 0 {
		if time.Now().UnixNano() > item.Expiration {
			return *new(V), false
		}
	}

	return item.Value, true
}

// Get получает значение.
func (c *Cache[K, V]) Get(key K) (V, bool) {
	c.mutex.RLock()
	defer c.mutex.RUnlock()

	return c.get(key)
}

// Delete удаляет значение.
func (c *Cache[K, V]) Delete(key K) {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	delete(c.items, key)
}

// GetAndDelete получает значение и удаляет его из списка.
func (c *Cache[K, V]) GetAndDelete(key K) (V, bool) {
	c.mutex.Lock()
	item, found := c.get(key)
	delete(c.items, key)
	c.mutex.Unlock()

	return item, found
}

// Exists проверяет, существует ли ключ.
func (c *Cache[K, V]) Exists(key K) bool {
	c.mutex.RLock()
	defer c.mutex.RUnlock()

	_, found := c.items[key]

	return found
}

// Clear очищает кеш.
func (c *Cache[K, V]) Clear() {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	clear(c.items)
}

// Count количество данных в карте.
func (c *Cache[K, V]) Count() int {
	c.mutex.RLock()
	defer c.mutex.RUnlock()

	return len(c.items)
}

// Append добавляет карту к кешу.
func (c *Cache[K, V]) Append(list map[K]V) int {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	for key, val := range list {
		c.set(key, val, c.defaultExpiration)
	}

	return len(c.items)
}

// Values возвращает значения в виде массива.
func (c *Cache[K, V]) Values() []V {
	c.mutex.RLock()
	defer c.mutex.RUnlock()

	result := make([]V, 0, len(c.items))
	for _, item := range c.items {
		result = append(result, item.Value)
	}

	return result
}

// Keys возвращает ключи в виде массива.
func (c *Cache[K, V]) Keys() []K {
	c.mutex.RLock()
	defer c.mutex.RUnlock()

	result := make([]K, 0, len(c.items))
	for key := range c.items {
		result = append(result, key)
	}

	return result
}

func (c *Cache[K, V]) cleanup() {
	for {
		<-time.After(c.cleanupInterval)
		if c.items != nil {
			c.clearExpiredKeys()
		}
	}
}

// clearExpiredKeys возвращает список "просроченных" ключей
func (c *Cache[K, V]) clearExpiredKeys() {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	for key, item := range c.items {
		if time.Now().UnixNano() > item.Expiration && item.Expiration > 0 {
			delete(c.items, key)
		}
	}
}
